<?php
/**
 * Created by PhpStorm.
 * User: tom_xia
 * Date: 2018/1/29
 * Time: 16:31
 */


class FFmpeg
{
    private $ffmpegPath = "";
    private $ffprobe = "";
    private $errorMsg = "";

    public function getLastError()
    {
        return $this->errorMsg;
    }

    public function __construct($is_GPU = false)
    {
        if ($is_GPU) {
            $this->ffmpegPath = "/app/build/bin/ffmpeg";
            $this->ffprobe = "/app/build/bin/ffprobe";
        } else {
            $this->ffmpegPath = "/usr/bin/ffmpeg";
            $this->ffprobe = "/app/ffmpeg/ffprobe";
            //            $this->ffmpegPath = "/usr/ffmpeg/ffmpeg";
            //            $this->ffprobe = "/usr/ffmpeg/ffprobe";
        }
    }

    /**
     * 获取视频流详细信息
     * @param $video
     * @return int
     */
    public function getDetail($video, $type = 1)
    {
        ob_start();
        if ($type == 1) {
            passthru("{$this->ffprobe} -v quiet -print_format json -show_streams  '$video' 2>&1");
            $content = ob_get_clean();
            $arr = json_decode($content, true);
            $data = [];
            if (!empty($arr['streams'])) {
                foreach ($arr['streams'] as $k => $v) {
                    if ($v['codec_type'] == "video") {
                        $data['width'] = $v['width'];
                        $data['height'] = $v['height'];
                        $data['duration'] = $v['duration'];
                        $data['bit_rate'] = $v['bit_rate'];
                    }
                }
                return $data;
            } else {
                return $data;
            }
        } else if ($type == 2) {
            passthru("{$this->ffprobe} -v quiet -print_format json -show_streams  '$video' 2>&1");
            $content = ob_get_clean();
            $arr = json_decode($content, true);
            return $arr;
        } else if ($type == 3) {
            passthru("{$this->ffprobe} -v quiet -print_format json -show_format  '$video' 2>&1");
            $content = ob_get_clean();
            $arr = json_decode($content, true);
            return $arr;
        } else if ($type == 4) {
            passthru("{$this->ffprobe} -v quiet -print_format json -show_streams '$video' 2>&1");
            $content = ob_get_clean();
            $arr = json_decode($content, true);
            $data = [];
            if (!empty($arr['streams'])) {
                foreach ($arr['streams'] as $k => $v) {
                    if ($v['codec_type'] == "video") {
                        $data['width'] = $v['width'];
                        $data['height'] = $v['height'];
                        $data['duration'] = $v['duration'];
                    }
                }
                return $data;
            } else {
                return $data;
            }
        }

    }


    public function formatFFmpegTime($duration)
    {
        $hour = intval($duration / 3600);
        $minute = intval(($duration - $hour * 3600) / 60);
        $second = intval(($duration - $hour * 3600 - $minute * 60));
        return sprintf("%02d", $hour) . ":" . sprintf("%02d", $minute) . ":" . sprintf("%02d", $second);
    }

    //添加水印
    public function overlay($src, $desc, $overImage, $x, $y)
    {
        $command = "{$this->ffmpegPath} -i '{$src}'  -i '{$overImage}' -acodec copy  -filter_complex 'overlay=x={$x}:y={$y}' {$desc} ";

        return exec($command);
    }

    //居中添加水印
    public function centerOverlay($src, $desc, $overImage)
    {
        $x = "(main_w-overlay_w)/2";
        $y = "(main_h-overlay_h)/2";
        $path = dirname($desc);
        if (!file_exists($path)) {
            mkdir($path, 0744, true);
        }
        return $this->overlay($src, $desc, $overImage, $x, $y);
    }

    //截图
    public function getCoverFromVideo($video, $image, $time = 0)
    {
        $dirName = dirname($image);
        if (!file_exists($dirName)) {
            exec("mkdir -m 777 -p {$dirName}");
        }
        $com = "{$this->ffmpegPath}  -i '{$video}' -y -f  image2  -ss {$time} -vframes 1  {$image}";
        ob_start();
        passthru($com, $status);
        ob_clean();
        if (file_exists($image)) {
            return true;
        } else {
            $this->errorMsg = "截图失败";
            return false;
        }
    }

    /**
     * 裁剪视频
     * @param $oriVideoPath string 原视频路径
     * @param $videoTempPath string 临时存放文件路径
     * @params $cutParams array 裁剪时间段
     * @return array | bool 裁剪视频的路径
     */
    public function cutVideo($oriVideoPath, $videoTempPath, array $cutParams)
    {
        //        if(!file_exists($oriVideoPath)){
        //            return false;
        //        }
        //临时存放目录不存在则创建
        if (!file_exists($videoTempPath)) {
            mkdir($videoTempPath, 0777, true);
        }
        $cutVideo = [];
        //获取文件名
        $oriFileName = pathinfo($oriVideoPath, PATHINFO_FILENAME);
        //获取后缀
        $fileExtension = pathinfo($oriVideoPath, PATHINFO_EXTENSION);
        if (function_exists("posix_getpid")) {
            $suffix = posix_getpid();
            $suffix = "_p_" . $suffix . "_" . time() . "_";
        } else {
            $suffix = "_r_" . mt_rand(10000, 99999) . "_" . time() . "_";
        }
        foreach ($cutParams as $key => $value) {
            $startTime = $value["start"];
            $duration = $value["duration"];
            $cutVideoFileName = "{$oriFileName}{$suffix}{$key}.{$fileExtension}";
            $outVideoPath = rtrim($videoTempPath, "/") . "/" . $cutVideoFileName;
            $command = "{$this->ffmpegPath} -y  -ss $startTime -t $duration -i '{$oriVideoPath}' -vcodec copy  -acodec copy  {$outVideoPath}";
            //            return $command;
            exec($command);

            if (file_exists($outVideoPath)) {
                $cutVideo[] = $outVideoPath;
            } else {
                $this->errorMsg = "【cutVideo】裁剪失败";
                return false;
            }
        }
        return $cutVideo;
    }

    /**
     * 合并视频
     * @param array $videos 要合并的视频文件
     * @param string $outVideo 合并完成后存储的视频文件
     * @param bool $isDelOriVideo 合并完成后是否删除原始视频
     * @return bool
     */
    public function mergeTVideo(array $videos, $outVideo, $isDelOriVideo = true)
    {
        if (empty($videos)) {
            return false;
        }
        //把文件写入临时文件
        $times = $this->getMsecTime();
        $tmp_txt = CDN_PATH . "/txt/" . $times . ".txt";
        if (!file_exists($tmp_txt)) {
            $myfile = fopen($tmp_txt, "w+");
        }
        //        return $myfile;
        foreach ($videos as $video) {
            if (!file_exists($video)) {
                return false;
            }
            fwrite($myfile, "file '" . $video . "'\n");
        }
        $command = "{$this->ffmpegPath} -f concat -safe 0 -i $tmp_txt -c copy {$outVideo}";
        //        return $command;
        exec($command);

        if (file_exists($outVideo)) {
            unlink($tmp_txt);//删除临时文件
            if ($isDelOriVideo == true) {
                foreach ($videos as $video) {
                    unlink($video);//删除临时视频
                }
            }
            return true;
        } else {
            return false;
        }
    }

    /**
     * 把视频转成hls视频
     * @param $video
     * @return boolean
     */
    public function genHLSVideo($video, $outM3u8File, $tsFormat = "o-%6d.ts")
    {
        //        if(!file_exists($video)){
        //            return false;
        //        }
        //修改ffmpeg进程执行目录
        chdir(dirname($outM3u8File));
        $command = "{$this->ffmpegPath} -y -i '{$video}'  -vcodec h264_nvenc -profile:v baseline -preset slow -pix_fmt yuv420p -b:v 1.5M -r 25 -bufsize 1M -keyint_min 50 -g 50  -ac 2 -ab 128k -vf scale=\"trunc(oh*a/2)*2:720\" -bsf:v h264_mp4toannexb -hls_time 10 -hls_list_size 0 -hls_segment_filename {$tsFormat} {$outM3u8File}";

        exec($command);
        if (file_exists($outM3u8File)) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * 获取视频长度
     * @param $video
     * @return int
     */
    public function getVideoLen($video)
    {
        ob_start();
        passthru("{$this->ffprobe} -v quiet -print_format json -show_format '$video' 2>&1");
        $content = ob_get_clean();
        $arr = json_decode($content, true);
        return isset($arr["format"]["duration"]) ? $arr["format"]["duration"] : 0;
    }

    /**
     * 获取视频流详细信息
     * @param $video
     * @return int
     */
    public function getVideoDetail($video)
    {
        ob_start();
        passthru("{$this->ffprobe} -v quiet -print_format json -show_streams  '$video' 2>&1");
        $content = ob_get_clean();
        $arr = json_decode($content, true);
        //        return $arr;
        $data = [];
        if (!empty($arr['streams'])) {
            foreach ($arr['streams'] as $k => $v) {
                if ($v['codec_type'] == "video") {
                    $data['width'] = $v['width'];
                    $data['height'] = $v['height'];
                }
            }
            return $data;
        } else {
            return $data;
        }
    }

    /**
     * 检查ts是否正确
     * @param $tsFile
     * @return bool
     */
    public function checkTs($tsFile)
    {
        $path = dirname($tsFile);
        exec("{$this->ffmpegPath} -i " . $tsFile . "  2> " . $path . "/tsinfo.txt ");
        $audionum = exec("cat " . $path . "/tsinfo.txt | grep 'Stream #0:' | grep 'Audio:' | wc -l");
        if ($audionum == 0) {
            return false;
        }
        $audionum = exec("cat " . $path . "/tsinfo.txt | grep 'Stream #0:' | grep 'Audio:' | grep '0 channels' | wc -l");
        if ($audionum > 0) {
            return false;
        }
        return true;
    }

    /**
     * 流转换为m3u8文件
     * @param $stream
     * @param $m3u8File
     * @param int $hls_time
     */
    public function streamToM3u8($stream, $m3u8File, $hls_time = 10)
    {
        $path = dirname($m3u8File);
        if (!is_dir($path)) {
            mkdir($path, 0777, true);
        }

        $command = "{$this->ffmpegPath} -re -i " . $stream . "  -c copy -b 1000k  -bsf:v h264_mp4toannexb -hls_time $hls_time -use_localtime 1 -hls_segment_filename '" . $path . "/v-%s.ts' " . $m3u8File . "2>/dev/null";
        exec($command);
    }

    /**
     * 音视频转换
     * @param $src string 源
     * @param $desc string  目的
     * @return bool
     */
    public function audioTrans($src, $desc)
    {
        $path = dirname($desc);
        if (!is_dir($path)) {
            mkdir($path, 0777, true);
        }
        $command = "{$this->ffmpegPath} -y  -i '{$src}' -acodec aac -ab 128k -ar 16k -ac 2 {$desc}";
        exec($command);
        if (file_exists($desc)) {
            return true;
        } else {
            $this->errorMsg = "音频转换失败";
            return false;
        }
    }

    /**
     * 音视频混编
     * @param $audioPaths array 音频地址
     * @param $videoPath string 视频地址
     * @param $outputPath string 输出的地址
     * @param $isDelOriAudio boolean 是否删除原始视频音频
     * @return bool
     */
    public function mixAudioVideo(array $audioPaths, $videoPath, $outputPath, $isDelOriAudio = true)
    {
        if (!$this->resourceExist($videoPath)) {
            $this->errorMsg = "【mergeAudioVideo】 视频文件{$videoPath}不存在";
            return false;
        }
        $outputDir = dirname($outputPath);
        if (!file_exists($outputDir)) {
            mkdir($outputDir, 0777, true);
        }
        //1.去除原始音频
        //        if($isDelOriAudio){
        //            $fileName = pathinfo($videoPath,PATHINFO_BASENAME);
        //            $uuid = Tools::UUID();
        //            $newVideoPath = $outputDir."/AN_{$uuid}_{$fileName}";
        //            if(!$this->delAudio($videoPath,$newVideoPath)){
        //                return false;
        //            }
        //            $videoPath = $newVideoPath;
        //        }
        //2.合成音频
        if (function_exists("posix_getpid")) {
            $suffix = posix_getpid();
            $accPath = "p_" . $suffix . "_" . time() . ".aac";
        } else {
            $accPath = "r_" . mt_rand(10000, 99999) . "_" . time() . ".aac";
        }
        $audioInputData = $outputDir . "/{$accPath}";
        if (!$this->mergeAudio($audioPaths, $audioInputData)) {
            return false;
        }
        $isShortest = "";
        $videoLength = $this->getVideoLen($videoPath);
        $audioLength = $this->getVideoLen($audioInputData);
        //        if($videoLength < $audioLength){
        //            $isShortest = "-shortest";
        //        }
        //3.合成音视频
        //        $command = "{$this->ffmpegPath} -y -i '{$videoPath}' '{$audioInputData}' -filter_complex amix=inputs={$audioNum}:duration=first:dropout_transition={$audioNum} -vcodec h264_nvenc -acodec acc -r 25 -b:v 1.5M -keyint_min 50 -g 50  -ac 2 -ab 128k '{$outputPath}'";
        $command = "{$this->ffmpegPath} -y -i '{$videoPath}' -i '{$audioInputData}'  -map 0:v:0 -map 1:a:0  -c:v copy -c:a aac   -ac 2 -ar 44100  -ab 128k -movflags faststart {$isShortest} '{$outputPath}'";
        exec($command);
        @unlink($audioInputData);
        if (file_exists($outputPath)) {
            return true;
        } else {
            $this->errorMsg = "【mergeAudioVideo】输出文件失败";
            return false;
        }
    }

    /**
     * 删除视频中的原始音频
     * @param $oriVideoPath
     * @param $outputPath
     * @return bool
     */
    public function delAudio($oriVideoPath, $outputPath)
    {
        $path = dirname($outputPath);
        if (!file_exists($path)) {
            mkdir($path, 0777, true);
        }
        if (!$this->resourceExist($oriVideoPath)) {
            $this->errorMsg = "【delAudio】 文件不存在";
            return false;
        }
        $command = "{$this->ffmpegPath} -y -i '{$oriVideoPath}' -vcodec copy -an '{$outputPath}'";
        echo $command . "\n";
        exec($command);
        if (!file_exists($outputPath)) {
            $this->errorMsg = "【delAudio】输出文件失败";
            return false;
        }
        return true;
    }

    /**
     * 修改原始音频声音
     * @param $audio
     * @param $outputPath
     * @param int $multiple 放大倍数 整数 可以为负数
     * @return bool
     */
    public function modifyVolume($audio, $outputPath, $multiple = 20)
    {
        $path = dirname($outputPath);
        if (!file_exists($path)) {
            mkdir($path, 0777, true);
        }
        if (!$this->resourceExist($audio)) {
            $this->errorMsg = "【modifyVolume】 文件不存在";
            return false;
        }
        $command = "{$this->ffmpegPath} -y -i '{$audio}' -af \"volume={$multiple}dB\"  '{$outputPath}'";
        echo $command . "\n";
        exec($command);
        if (!file_exists($outputPath)) {
            $this->errorMsg = "【modifyVolume】输出文件失败";
            return false;
        }
        return true;
    }


    /**
     * 合并多个音频
     * @param array $audioPaths 音频地址
     * @param string $outputPath 输出地址
     * @return boolean
     */
    public function mergeAudio(array $audioPaths, $outputPath)
    {
        $path = dirname($outputPath);
        if (!file_exists($path)) {
            mkdir($path, 0777, true);
        }
        $audioInputData = "";
        foreach ($audioPaths as $audioPath) {
            if (!$this->resourceExist($audioPath)) {
                $this->errorMsg = "【mergeAudio】 音频文件{$audioPath}不存在";
                return false;
            }
            $audioInputData .= " -i '{$audioPath}'";
        }
        $audioNum = count($audioPaths);
        $command = "{$this->ffmpegPath}  -y $audioInputData  -filter_complex amix=inputs={$audioNum}:duration=first:dropout_transition={$audioNum}  -vol 500 -ac 2 -ar 44100  -ab 128k {$outputPath}";
        exec($command);
        if (file_exists($outputPath)) {
            return true;
        } else {
            $this->errorMsg = "音频合成失败";
            return false;
        }
    }

    /**
     * 检查资源是否存在
     * @param $resourcePath string 资源路径
     * @return bool
     */
    private function resourceExist($resourcePath)
    {
        if (filter_var($resourcePath, FILTER_VALIDATE_URL)) {
            //            if(!get_headers($resourcePath)){
            //                return false;
            //            }
            return true;
        } else {
            if (!file_exists($resourcePath)) {
                return false;
            }
        }
        return true;
    }

    /**
     * 转码
     * @param $noAudioOriVideoPath
     * @param $noAudioOutPutPath
     * @return bool
     */
    public function transCode($noAudioOriVideoPath, $noAudioOutPutPath)
    {
        $path = dirname($noAudioOutPutPath);
        if (!file_exists($path)) {
            mkdir($path, 0777, true);
        }
        $command = "{$this->ffmpegPath} -y  -i '{$noAudioOriVideoPath}'   -c:v h264 -c:a aac -r 25 -b:v 1.5M -keyint_min 50 -g 50  -ac 2 -ar 44100  -ab 128k '{$noAudioOutPutPath}'";
        exec($command);
        if (file_exists($noAudioOutPutPath)) {
            return true;
        } else {
            $this->errorMsg = "视频转码失败";
            return false;
        }
    }

    /**
     * 获取毫秒级别的时间戳
     */
    public function getMsecTime()
    {
        list($msec, $sec) = explode(' ', microtime());
        $msectime = (float)sprintf('%.0f', (floatval($msec) + floatval($sec)) * 1000);
        return $msectime;
    }
}